package org.traffometer.packet;

import java.util.Date;

import org.traffometer.packet.location.Location;

/**
 * Implements a representation of a packet of traffic information that is essential to the
 * TraffoMeter system. The TraffoPacket contains a pair of &lt;location, timestamp&gt; data values 
 * along with a vehicle-type. A TraffoPacket can be represented as the following tuple: 
 * 
 * {&lt;L1, T1&gt;, &lt;L2, T2&gt;, V}, 
 * 
 * which can be interpreted as representing the average speed that a vehicle of type V moved 
 * between the two points L1 and L2 on a particular day and time. This data should be sent to 
 * the TraffoMeter server as soon as possible after the second &lt;L2, T2&gt; data value is 
 * collected. 
 *
 * @author Aaron A. Kagawa
 * @version $Id$
 */
public class TraffoPacket {
  
  private VehicleType vehicleType = null;
  private LocationTimestampPair pairOne = null;
  private LocationTimestampPair pairTwo = null;
  
  /**
   * Constructs a TraffoPacket object that contains a pair of location-timestamp representations
   *   and a vehicle type. The parameters for this constructor have the following constraints:
   * <ul>
   * <li>All of the following parameters must not be null. 
   * <li>The timestamp for the second pair must occur after the first and must not be the same.
   * <li>The location for the second pair must not be different for the first.
   * </ul> 
   * Not that none of these requirements are currently explicitly enforced. 
   * 
   * @param vehicleType The vehicle type. 
   * @param locationOne The location of the first location-timestamp pair.
   * @param timestampOne The timestamp of the first location-timestamp pair.
   * @param locationTwo The location of the second location-timestamp pair.
   * @param timestampTwo The timestamp of the first location-timestamp pair.
   */
  public TraffoPacket(VehicleType vehicleType, Location locationOne, Date timestampOne, 
      Location locationTwo, Date timestampTwo) {
    this.vehicleType = vehicleType;
    this.pairOne = new LocationTimestampPair(locationOne, timestampOne);
    this.pairTwo = new LocationTimestampPair(locationTwo, timestampTwo);
  }
  
  /**
   * Returns the vehicle type associated with this TraffoPacket.
   * @return The vehicle type.
   */
  public VehicleType getVehicleType() {
    return this.vehicleType;
  }
  
  /**
   * Returns the first LocationTimestampPair.
   * @return The first LocationTimestampPair.
   */
  public LocationTimestampPair getLocationTimestampPairOne() {
    return this.pairOne;
  }

  /**
   * Returns the second LocationTimestampPair.
   * @return The second LocationTimestampPair.
   */
  public LocationTimestampPair getLocationTimestampPairTwo() {
    return this.pairTwo;
  }
  
  /**
   * Returns the distance between the first and second LocationTimestampPair. The Haversine
   *   Formula is used to calculate the distance.<br>
   * Haversine Formula: <br>
   * presuming a spherical Earth with radius R (see below), and the locations of the two points 
   * in spherical coordinates (longitude and latitude) are lon1,lat1 and lon2,lat2 
   * <pre>
   *   dlon = lon2 - lon1
   *   dlat = lat2 - lat1
   *   a = sin^2(dlat/2) + cos(lat1) * cos(lat2) * sin^2(dlon/2)
   *   c = 2 * arcsin(min(1,sqrt(a)))
   *   d = R * c
   * </pre>
   * I'm not sure what the difference is but I found another formula:
   * <pre>
   *   dlon = lon2 - lon1
   *   dlat = lat2 - lat1
   *   a = (sin(dlat/2))^2 + cos(lat1) * cos(lat2) * (sin(dlon/2))^2
   *   c = 2 * atan2(sqrt(a), sqrt(1-a))
   *   d = R * c
   * </pre>
   * The radius of the earth is R = 6367 km = 3956 mi 
   * @return The distance between to locations.
   */
  public double distanceBetween() {
    //double earthRadius = 6371 ; //in km
    double earthRadius = 3956; //in miles
    double pairOneLongitude = this.pairOne.getLocation().getLongitude().toSexagesimal();
    double pairOneLatitude = this.pairOne.getLocation().getLatitude().toSexagesimal();
    //System.out.println("pairOneLongitude = " + pairOneLongitude);
    //System.out.println("pairOneLatitude = " + pairOneLatitude);
    double pairTwoLongitude = this.pairTwo.getLocation().getLongitude().toSexagesimal();
    double pairTwoLatitude = this.pairTwo.getLocation().getLatitude().toSexagesimal();
    //System.out.println("pairTwoLongitude = " + pairTwoLongitude);
    //System.out.println("pairTwoLatitude = " + pairTwoLatitude);
    
    double radianConverter = Math.PI / 180.0;
    double pairOneLongitudeRadian = pairOneLongitude * radianConverter; 
    double pairOneLatitudeRadian = pairOneLatitude * radianConverter; 
    double pairTwoLongitudeRadian = pairTwoLongitude * radianConverter; 
    double pairTwoLatitudeRadian = pairTwoLatitude * radianConverter; 
    
    double longitudeDifference = pairTwoLongitudeRadian - pairOneLongitudeRadian;
    //System.out.println("longitudeDifference = " + longitudeDifference);
    //System.out.println("should be -0.00011150714665486561");
    double latitudeDifference = pairTwoLatitudeRadian - pairOneLatitudeRadian;
    //System.out.println("latitudeDifference = " + latitudeDifference);
    //System.out.println("should be 0.000019392547244423408");
    
    double variableA = Math.pow(Math.sin(latitudeDifference / 2.0), 2) 
      + Math.cos(pairOneLatitudeRadian)
      * Math.cos(pairTwoLatitudeRadian) 
      * Math.pow(Math.sin(longitudeDifference / 2.0), 2);

    //System.out.println("variableA = " + variableA);
    //System.out.println("should be 2.7909065451122327e-9");
    
    double variableC = 2 * Math.atan2(Math.sqrt(variableA), Math.sqrt(1.0 - variableA));
    double distance = earthRadius * variableC;
    
    //System.out.println("distance = " + distance);
    //System.out.println("should be   0.41 miles = 0.65983104 kilometers");
    return distance;
  }
  
  /** 
   * {@inheritDoc}
   * The toString will have the following format.
   * {&lt;L1, T1&gt;, &lt;L2, T2&gt;, V},  
   */
  public String toString() {
    return "<" + this.pairOne.toString() + ">, <" + this.pairTwo + ", " + this.vehicleType;
  }

  /**
   * The main method of the TraffoPacket application.
   * @param args Ignored.
   */
  public static void main(String[] args) {
    System.out.println("Hello, World!");
  }
}